#include "connection_mgr.h"
#include "ikcp.h"
#include "kcp_module.h"
#include <string.h>
#include "wrapper_object_pool.h"

namespace KCP {
// sending buff pool.
struct SBuffer {
	char buff[KCP_PACKET_MAX_SIZE];
	SBuffer() { buff[0] = 0; }
	DYNAMIC_SIMPLE_DECLARE(SBuffer, 32, 8);
};
DYNAMIC_SIMPLE_IMPLEMENT(SBuffer);

CConnectionMgr::CConnectionMgr(asio::io_service& ioService,
	const std::string& ip, uint16 port) : 
	m_udpSocket(ioService, udp::endpoint(asio::ip::address::from_string(ip), port)) {
	startAsyncRecv();
}

CConnectionMgr::CConnectionMgr(asio::io_service& ioService) : m_udpSocket(ioService) {
	m_udpSocket.open(udp::v4());
	startAsyncRecv();
}

CConnectionMgr::~CConnectionMgr() {
	this->stop();
}

void CConnectionMgr::stop() {
	m_connections.Stop();
	m_udpSocket.cancel();
	m_udpSocket.close();
}

void CConnectionMgr::disconnect(CConnection *conn, const std::string &msg) {
	if (conn == nullptr) {
		return;
	}
	// release and remove it.
	m_connections.Remove(conn->GetConv());
	CConnection::release(conn);
}

void CConnectionMgr::set_callback(const std::function<event_callback_t>& func) {
	m_eventCallback = func;
}

int CConnectionMgr::event_callback_func(CConnection *conn,
	eEventType eventType,const char* msg,int len) {
	return m_eventCallback(conn, eventType, msg, len);
}

// UDP server shake with client to establish connection.
void CConnectionMgr::handleConnectPack(const std::string &data,const udp::endpoint& endPoint) {
	CConnection *conn = nullptr;
	if (m_mode == mode_server) {
		// server: send conv id to client.
		conn = m_connections.Create(*this, CConnectionMap::GetNextConv(), endPoint);
		this->sendEstablishPack(conn->GetConv(), endPoint);
        
		// set connecting state.
		conn->setState(CConnection::conn_state_connecting);
		conn->setConnectingTime(GetTickCount());
	} else {
		// client: recv conv id and create connection with conv.
		const int size = 64;
		const size_t convFlagSize = sizeof(CREATE_CONV);
		char num[size] = {0};

		// try to get conv id
		size_t j = 0;
		for (size_t i= convFlagSize-1; j < size && i < data.size(); i++,j++) {
			num[j] = data.c_str()[i];
		}
		num[j] = 0;

		// Parse conv id from packet.
		conn = m_connections.Create(*this, atoi(num), endPoint);
		conn->setState(CConnection::conn_state_connected);
	}

	// Try to deal connection.
	assert(conn != nullptr && "connection is null");
	if (conn == nullptr) {
		return;
	}
	handleKCPConnect(conn, data.c_str(), data.size());
}

void CConnectionMgr::sendEstablishPack(uint32 convId, const udp::endpoint& endPoint) {
	char buff[1024] = { 0 };
	int len = std::snprintf(buff, sizeof(buff)-1, CREATE_CONV"%d", convId);
	if (len < 0) {
		return;
	}
	buff[len-1] = 0;
	this->send_udp(buff, len, endPoint);
}

CConnection* CConnectionMgr::find(uint32 conv) {
	return m_connections.FindConnection(conv);
}

void CConnectionMgr::handleKCPDisconnect(CConnection *conn, const std::string &msg) {
	this->event_callback_func(conn, eDisconnect, msg.c_str(), msg.size());
}

int CConnectionMgr::handleKCPData(CConnection *conn, const char* msg, int len) {
	return this->event_callback_func(conn, eRcvMsg, msg, len);
}

void CConnectionMgr::handleKCPConnect(CConnection* conn, const char* msg, int len) {
	this->event_callback_func(conn, eConnect, msg, len);
}

bool CConnectionMgr::handleDataPack(const std::string &data, const udp::endpoint& endPoint) {
	IUINT32 conv;
	int ret = ikcp_get_conv(data.c_str(), data.size(), &conv);
	if (ret == 0) {
		ADebug("can not find connection conv id.");
		return false;
	}

	// Get Connection from conv id.
	CConnection* conn = this->find(conv);
	if (conn == nullptr) {
		ADebug("can not find %d connection", conv);
		return false;
	}

	// Recv data, then set connected state.
	conn->setState(CConnection::conn_state_connected);

	// input connection.
	conn->input(data.c_str(), data.size(), endPoint);
	return true;
}

bool CConnectionMgr::checkConnectPack(const std::string &data) {
	if (m_mode == mode_server) {
		if (memcmp(data.c_str(), NEW_CONNECTION, data.size()) == 0) {
			return true;
		}
	} else if (m_mode == mode_client){
		if (strstr(data.c_str(), CREATE_CONV) != nullptr) {
			return true;
		}
	}
	return false;
}

// Async recv data.
void CConnectionMgr::startAsyncRecv() {
	// async recv hook.
	m_udpSocket.async_receive_from(
		// data recv buff,
		asio::buffer(m_recvBuff, KCP_PACKET_MAX_SIZE),

		// socket address info, as return value.
		m_endPoint,

		// recv data callback function.
		[this](std::error_code error, std::size_t byteRecv) {
		  if (!error) {
			 // !!copy data to main thread.
			 std::string data(m_recvBuff, byteRecv);

			 // !!copy endpoint to main thread.
			 udp::endpoint ep(m_endPoint);

			 // post message to main thread.
			 asio::post(CKCPModule::instance().GetIOService(),
				// post udp data to main io_service to execute.
				[this, data = std::move(data), ep = std::move(ep)]() {
				  if (this->checkConnectPack(data)) {
					  // handle connect packet.
					  this->handleConnectPack(data,ep);
				  } else {
					  // handle data packet.
					  this->handleDataPack(data,ep);
				  }
			    }
			 );
		  }

		  // start async recv again.
		  this->startAsyncRecv();
	    }
	);
}

void CConnectionMgr::update() {
	m_connections.Update(GetTickCount());
}

// Async send message to peer.
void CConnectionMgr::send_udp(const char *msg,int len,const asio::ip::udp::endpoint& endpoint) {
	if (len <= 0 || len > KCP_PACKET_MAX_SIZE) {
		ADebug("find invalid len:%d",len);
		return;
	}

	SBuffer *pBuff = SBuffer::fetch_obj();
	assert(sizeof(pBuff->buff) >= len && "protocol size error");
	memcpy(pBuff->buff,msg,len);

	// async send data.
	m_udpSocket.async_send_to(
		// send buffer.
		asio::buffer(pBuff->buff, len),

		// remote end point.
		endpoint,

		// async send callback.
		[this, len, pBuff, endpoint](std::error_code error, std::size_t byteSend) {
		    if (!error) {
				decltype(len) realSendBytes(byteSend);
				if (realSendBytes != len) {
					ADebug("send message len error,recv:%d,all:%d", len, byteSend);
					// if not all send ok, then send it again
					if (len > realSendBytes) {
						this->send_udp(pBuff->buff + byteSend, len - byteSend, endpoint);
					}
				}
			} else {
				ADebug("send message error:%d", error.value());
			}

			// just post pBuff to main thread to release it.
			asio::post(CKCPModule::instance().GetIOService(),
				[this,pBuff]() {
				    SBuffer::release_obj(pBuff);
			    }
			);
	    }
	);
}
}